import { decodeCharset } from '../utils/encodings';
import { logger } from '../utils/log';

type RtfCommand = {
  name: string;
  parameter?: number;
}
type RtfText = string;
type RtfGroup = (RtfCommand | RtfText | RtfGroup)[];
type RtfItem = RtfCommand | RtfText | RtfGroup;

class RtfReader {
  index = 0;
  constructor(public rtf: string) {
  }

  end() {
    return this.index >= this.rtf.length;
  }
  peek(n: number = 1): string {
    return this.rtf.substring(this.index, this.index + n);
  }
  read(n: number): string {
    const s = this.rtf.substring(this.index, this.index + n);
    this.index += n;
    return s;
  }
  readToNext(): string {
    let r: string = '';
    for (let i = this.index; i < this.rtf.length; i++) {
      const ch = this.rtf[i];
      if (ch == '{' || ch == '}' || (ch == '\\' && this.rtf[i + 1] != "'")) {
        r = this.rtf.substring(this.index, i);
        this.index = i;
        break;
      }
    }
    return r;
  }
  skip() {
    let ch = this.rtf[this.index];
    while (ch == '\r' || ch == '\n' || ch == ' ' || ch == '\t') {
      this.index += 1;
      ch = this.rtf[this.index];
    }
  }
  read2(): string {
    let i = this.index + 1;
    while (i < this.rtf.length && 'a' <= this.rtf[i] && this.rtf[i] <= 'z') {
      i++;
    }
    if (this.rtf[i] == ' ') {
      i++;
      const s = this.rtf.substring(this.index, i);
      this.index = i;
      return s;
    }
    while (i < this.rtf.length && 'a' <= this.rtf[i] && this.rtf[i] <= 'z') {
      ;
    }
  }
  readToken(): string {
    let ch = this.rtf[this.index];
    let result: string = ch;
    let j = 0;
    switch (ch) {
      case '{':
      case '}':
        this.index += 1;
        result = ch;
        break;
      case '\\':
        j = this.index + 1;
        ch = this.rtf[j];
        if (ch == "'") {
          result = this.readToNext();
        } else if (ch < 'a' || ch > 'z') {
          result = this.read(2);
        } else {
          do {
            j += 1;
            ch = this.rtf[j];
            if (ch == ' ') {
              j += 1;
              break;
            }
            if (!/[a-z\d-]/.test(ch)){
              break;
            }
          } while (j < this.rtf.length);
          result = this.rtf.substring(this.index, j);
          this.index = j;
        }
        break;
      default:
        result = this.readToNext();
        break;
    }
    return result;
  }
}

type RtfControl = {
  control: string,
  parameter?: number
}

class Reader {
  cursor: number = 0
  constructor(public data: Uint8Array) {
  }
  peek(n: number = 1): Uint8Array {
    return this.data.subarray(this.cursor, this.cursor + n);
  }
  read(n: number = 1): Uint8Array {
    const d = this.data.subarray(this.cursor, this.cursor + n);
    this.cursor += n;
    return d;
  }
  discard(n: number = 1) {
    this.cursor += n;
  }

  readControl(): RtfControl {
    let i = this.cursor + 1;
    let ch = this.data[i];
    let control = '';
    let parameter: number | undefined;
    while (i < this.data.byteLength &&
      ((ControlChars.CHAR_A <= ch && ch <= ControlChars.CHAR_Z) ||
        (ControlChars.CHAR_a <= ch && ch <= ControlChars.CHAR_z))) {
      i++;
      ch = this.data[i];
      control = decodeCharset(this.data.subarray(this.cursor + 1, i), 'ascii');
    }
    let j = i;
    while (j < this.data.byteLength &&
      ((ControlChars.CHAR_0 <= ch && ch <= ControlChars.CHAR_9) ||
        ControlChars.DASH == ch)) {
      j++;
      ch = this.data[j];
      parameter = parseInt(decodeCharset(this.data.subarray(i, j), 'ascii'));
    }
    // const r = this.data.subarray(this.cursor, i);
    this.cursor = j;
    if (ch == ControlChars.SPACE) {
      this.cursor++;
    }
    return { control, parameter };
  }

  skip(): void {
    while (
      this.cursor < this.data.byteLength &&
      this.data[this.cursor] != 10 && this.data[this.cursor] != 13
    ) {
      this.cursor++;
    }
  }
  end(): boolean {
    return this.cursor < this.data.byteLength;
  }
}

enum ControlChars {
  LEFT_BRACE = '{'.charCodeAt(0),
  RIGHT_BRACE = '}'.charCodeAt(0),
  BACKSLASH = '\\'.charCodeAt(0),
  CHAR_A = 'A'.charCodeAt(0),
  CHAR_Z = 'Z'.charCodeAt(0),
  CHAR_a = 'a'.charCodeAt(0),
  CHAR_z = 'z'.charCodeAt(0),
  CHAR_0 = '0'.charCodeAt(0),
  CHAR_9 = '9'.charCodeAt(0),
  DASH = '-'.charCodeAt(0),
  SPACE = ' '.charCodeAt(0),
}

export class RtfParser {
  reader: Reader;
  constructor(rtf: Uint8Array) {
    this.reader = new Reader(rtf);
  }
  parse() {
    const root: RtfNode = {
      children: []
    }
    let current = root;
    const stack: RtfNode[] = [root];
    const reader = this.reader;
    let ch = reader.read(1)[0]; // ch == '{'
    while (!reader.end()) {
      let buff = reader.peek(2);
      ch = buff[0];
      switch (ch) {
        case ControlChars.BACKSLASH:
          ch = buff[1];
          if ((ControlChars.CHAR_A <= ch && ch <= ControlChars.CHAR_Z) ||
            (ControlChars.CHAR_a <= ch && ch <= ControlChars.CHAR_z)) {
            this.processControlWord(current);
          } else {
            this.processControlSymbol(current, ch);
          }
          break;
        case ControlChars.LEFT_BRACE:
          let n: RtfNode = {
            children: []
          }
          current.children.push(n);
          stack.push(n);
          current = n;
          break;
        case ControlChars.RIGHT_BRACE:
          break;
      }
    }
  }

  processControlWord(node: RtfNode) {
    const c = this.reader.readControl();
    node.children.push(c);
  }

  processControlSymbol(node: RtfNode, symbol: number) {
    const s = String.fromCharCode(symbol);
    node.children.push(s);
  }
}

type RtfNode = {
  children: (RtfControl | RtfNode | string | Uint8Array)[];
}

function processRtfControl(node: RtfNode, command: string) {
  ;
}

const CONTROL_CHARS = 'abcdefghijklmnopqrstuvwxyz1234567890-'
export function parseRtf(s: string): RtfGroup {
  logger.info('parse rtf', s.length);
  const reader = new RtfReader(s);
  const stack: RtfGroup[] = [];
  let g: RtfGroup | undefined = undefined;
  let root: RtfGroup | undefined = undefined;
  while (!reader.end()) {
    const token = reader.readToken();
    if (token == '{') {
      let ng: RtfGroup = [];
      if (g) {
        g.push(ng);
      }
      g = ng;
      stack.push(g);
      logger.info('rtf stack push', stack.length)
      if (!root) {
        root = g;
      }
    } else if (token == '}') {
      stack.pop()!;
      reader.skip();
      g = stack[stack.length - 1];
      logger.info('rtf stack pop', stack.length)
    } else {
      g.push(token);
    }
  }
  return root;
}
